package core.molecules;

import java.awt.HeadlessException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import core.molecules.atoms.Atom;
import core.molecules.atoms.subatomic.SubatomicParticle;



/**
 * A core set of methods to be used in most Molecules. this Set sorts by each subparticle's
 * ID number, therefore if the molecules in the PDB are inserted in the correct spatial order,
 * then the ordering of this set will correspond closely to the actual physical ordering of the molecule
 * @author Julian
 *
 * @param <T>
 */
public abstract class MoleculeBase<T extends IMolecule> extends ArrayList<T> implements IMolecule {



	@Override
	public boolean add(T e) {
		myLast = e;
		return super.add(e);
	}

	public T last(){
		return myLast;
	}
	
	public Integer getHighestID(){
		int max = 0;
		for (T m: this){
			if (m.getID() > max)
				max = m.getID();
		}
		return max;
	}

	/**
	 * a basic integer identifier for the molecule
	 */
	protected Integer myID;
	protected T myLast;
	
	@Override
	public Integer getID() {
		return myID;
	}


	public MoleculeBase(int id) {
		super();
		myID = id;
	}

	public MoleculeBase(Collection<? extends T> c, int id) {
		super(c);
		myID = id;
	}


	@Override
	public int compareTo(IMolecule o) {
		return this.getID().compareTo(o.getID());
	}


	/**
	 * Returns a list of submolecules of the same class as passed. This method uses generic casting to
	 * enable complete access to subparticle methods and prevent incorrect casting.
	 * @param <S>
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <S extends IMolecule> List<S> getMoleculesOfType(Class<S> clazz){
		List<S> mols = new ArrayList<S>();
		for (IMolecule m: this){
			if (m.getClass().isAssignableFrom(clazz)){
				mols.add((S) m);
			}
		}
		return mols;
	}
	
	/**
	 * Add <num> subParticles/Molecules of the given class to this molecule
	 * @param clazz
	 * @param num
	 * @return
	 */
	public double addSubMolecules (Class<? extends T> clazz, int num){
		while (num > 0){
			try {
				add(clazz.newInstance());
			} catch (Exception e) {
				e.printStackTrace();
			}
			num--;
		}
		return this.getMoleculesOfType(clazz).size();
	}
	


	/**
	 * Remove <num> subParticles/Molecules of the given class to this molecule. If num > the actual number of the passed type of molecule
	 * within this molecule, then if will remove all target submolecules that remain and then stop.
	 * @param clazz
	 * @param num
	 * @return
	 */
	public double removeParticles (Class<? extends T> clazz, int num){
		while (num > 0){
			
			for(T m : this){
				if (m.getClass().isAssignableFrom(clazz)){
					this.remove(m);
					break;
				}
			}
				
			
			num--;
		}
		return this.getMoleculesOfType(clazz).size();
	}
	
	
	
	
	@Override
	public double getMass() {
		double mass = 0;
		for (T m: this)
			mass += m.getMass();
		return mass;
	}

	@Override
	public double getNumSubParticle() {
		return this.size();
	}
	
	@Override
	public int getOxidationState() {
		int oxState = 0;
		for (T m: this)
			oxState += m.getOxidationState();
		return oxState;
	}

	@Override
	public String toString() {
		String sign = "";
		if (this.getOxidationState() > 0) sign = "+";
		String out = this.getClass().getSimpleName() +"\n";
		for (T m: this){
			out += "\t" + m.toString();
		}
		
		return out;
	}
	

	@Override
	public void trimEmptyMolecules(){
		Set<T> removeSet = new TreeSet<T>();
		for (T m: this){
			if (m.isEmpty()){
				removeSet.add(m);
			}
			else
				m.trimEmptyMolecules();
		}
		this.removeAll(removeSet);
	}
	
	/**
	 * Check if this molecule contains an element of ID id
	 * @return
	 */
	public boolean containsSubmoleculeByID(int id){
		for (T m: this){
			if (m.getID().equals(id))
				return true;
		}
		return false;
	}
	
	/**
	 * returns the submolecule corresponding to this id
	 * @return
	 */
	public T getSubmoleculeByID(int id){
		for (T m: this){
			if (m.getID().equals(id))
				return m;
		}
		return null;
	}


	@Override
	public boolean equals(IMolecule m) {
		return false;
	}


	@Override
	public int hashCode() {
		return super.hashCode();
	}
	
	
}
